/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.kafka.clients.producer;

import org.apache.kafka.clients.ClientDnsLookup;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.common.config.AbstractConfig;
import org.apache.kafka.common.config.ConfigDef;
import org.apache.kafka.common.config.ConfigDef.Importance;
import org.apache.kafka.common.config.ConfigDef.Type;
import org.apache.kafka.common.config.ConfigException;
import org.apache.kafka.common.config.SecurityConfig;
import org.apache.kafka.common.metrics.Sensor;
import org.apache.kafka.common.record.CompressionType;
import org.apache.kafka.common.security.auth.SecurityProtocol;
import org.apache.kafka.common.serialization.Serializer;
import org.apache.kafka.common.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import static org.apache.kafka.common.config.ConfigDef.Range.atLeast;
import static org.apache.kafka.common.config.ConfigDef.Range.between;
import static org.apache.kafka.common.config.ConfigDef.ValidString.in;

/**
 * Configuration for the Kafka Producer. Documentation for these configurations can be found in the <a
 * href="http://kafka.apache.org/documentation.html#producerconfigs">Kafka documentation</a>
 */
// Kafka 生产者的配置。这些配置的文档可以在 Kafka 文档中找到
public class ProducerConfig extends AbstractConfig {
    private static final Logger log = LoggerFactory.getLogger(ProducerConfig.class);

    /*
     * NOTE: DO NOT CHANGE EITHER CONFIG STRINGS OR THEIR JAVA VARIABLE NAMES AS THESE ARE PART OF THE PUBLIC API AND
     * CHANGE WILL BREAK USER CODE.
     */

    private static final ConfigDef CONFIG;

    // ----------------------------------------------------------------

    /** <code>bootstrap.servers</code> */
    public static final String BOOTSTRAP_SERVERS_CONFIG = CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG;

    // ----------------------------------------------------------------

    /** <code>client.dns.lookup</code> */
    public static final String CLIENT_DNS_LOOKUP_CONFIG = CommonClientConfigs.CLIENT_DNS_LOOKUP_CONFIG;

    // ----------------------------------------------------------------

    /** <code>metadata.max.age.ms</code> */
    public static final String METADATA_MAX_AGE_CONFIG = CommonClientConfigs.METADATA_MAX_AGE_CONFIG;
    private static final String METADATA_MAX_AGE_DOC = CommonClientConfigs.METADATA_MAX_AGE_DOC;

    // ----------------------------------------------------------------

    /** <code>metadata.max.idle.ms</code> */
    public static final String METADATA_MAX_IDLE_CONFIG = "metadata.max.idle.ms";
    /**
     * Controls how long the producer will cache metadata for a topic that's idle.
     * If the elapsed time since a topic was last produced to exceeds the metadata idle duration,
     * then the topic's metadata is forgotten and the next access to it will force a metadata fetch request.
     */
    /*
     控制生产者对空闲主题（topic）元数据的缓存时长。
     如果某个主题超过设定的空闲时长未被写入数据，
     其元数据将被清除，下次访问时将强制触发元数据获取请求。
     */
    private static final String METADATA_MAX_IDLE_DOC =
            "Controls how long the producer will cache metadata for a topic that's idle. If the elapsed " +
            "time since a topic was last produced to exceeds the metadata idle duration, then the topic's " +
            "metadata is forgotten and the next access to it will force a metadata fetch request.";

    // ----------------------------------------------------------------

    /** <code>batch.size</code> */
    public static final String BATCH_SIZE_CONFIG = "batch.size";
    /**
     * The producer will attempt to batch records together into fewer requests whenever multiple records are being sent to the same partition.
     * This helps performance on both the client and the server.
     * This configuration controls the default batch size in bytes.
     * <p>No attempt will be made to batch records larger than this size.
     * <p>Requests sent to brokers will contain multiple batches, one for each partition with data available to be sent.
     * <p>A small batch size will make batching less common and may reduce throughput (a batch size of zero will disable batching entirely).
     * A very large batch size may use memory a bit more wastefully as we will always allocate a buffer of the specified batch size in anticipation of additional records.
     * <p>Note: This setting gives the upper bound of the batch size to be sent.
     * If we have fewer than this many bytes accumulated for this partition, we will 'linger' for the <code>linger.ms</code> time waiting for more records to show up.
     * This <code>linger.ms</code> setting defaults to 0, which means we'll immediately send out a record even the accumulated batch size is under this <code>batch.size</code> setting.
     *
     */
    // 生产者批处理机制详解
    // 核心功能
    // 自动批处理：当多条记录发送至相同分区时，自动合并为更少的请求
    // 性能优化：显著提升客户端和服务端的处理效率
    // 关键配置：batch.size
    // 特性	    说明
    // 单位	    字节（bytes）
    // 上限控制	不处理超过此大小的记录
    // 请求结构	每个请求包含多个分区批次
    // 默认行为	立即发送（linger.ms=0）
    // 大小配置影响
    // 过小值（包括零）：
    // 批处理频率降低
    // 可能显著降低吞吐量
    // 完全禁用批处理（当设为0时）
    //
    // 过大值：
    // 内存预分配可能造成浪费
    // 始终预留指定大小的缓冲区
    // 与linger.ms的协同
    // 默认立即发送：
    // 即使累积量未达batch.size
    // 通过linger.ms=0实现
    // 延迟等待机制：
    // 当累积量不足batch.size时
    // 等待linger.ms时长以获取更多记录
    // 典型应用：设置linger.ms=1可显著提升批量率
    // 工程建议：根据实际消息大小和吞吐需求，建议设置16KB-1MB之间的值。同时配合linger.ms=5-100可获得最佳吞吐/延迟平衡。监控batch-size-avg指标可验证配置效果。
    private static final String BATCH_SIZE_DOC = "The producer will attempt to batch records together into fewer requests whenever multiple records are being sent"
                                                 + " to the same partition. This helps performance on both the client and the server. This configuration controls the "
                                                 + "default batch size in bytes. "
                                                 + "<p>"
                                                 + "No attempt will be made to batch records larger than this size. "
                                                 + "<p>"
                                                 + "Requests sent to brokers will contain multiple batches, one for each partition with data available to be sent. "
                                                 + "<p>"
                                                 + "A small batch size will make batching less common and may reduce throughput (a batch size of zero will disable "
                                                 + "batching entirely). A very large batch size may use memory a bit more wastefully as we will always allocate a "
                                                 + "buffer of the specified batch size in anticipation of additional records."
                                                 + "<p>"
                                                 + "Note: This setting gives the upper bound of the batch size to be sent. If we have fewer than this many bytes accumulated "
                                                 + "for this partition, we will 'linger' for the <code>linger.ms</code> time waiting for more records to show up. "
                                                 + "This <code>linger.ms</code> setting defaults to 0, which means we'll immediately send out a record even the accumulated "
                                                 + "batch size is under this <code>batch.size</code> setting.";

    // ----------------------------------------------------------------

    /** <code>partitioner.adaptive.partitioning.enable</code> */
    public static final String PARTITIONER_ADPATIVE_PARTITIONING_ENABLE_CONFIG = "partitioner.adaptive.partitioning.enable";
    /**
     * When set to 'true', the producer will try to adapt to broker performance and produce more messages to partitions hosted on faster brokers.
     * If 'false', producer will try to distribute messages uniformly.
     * Note: this setting has no effect if a custom partitioner is used
     */
    /*
     * 当设置为 'true' 时，生产者会尝试适应 broker 的性能，向托管在性能更快的 broker 上的分区发送更多消息。
     * 如果设置为 'false'，生产者会尝试均匀地分发消息。
     * 注意：如果使用了自定义分区器（partitioner），此设置将不起作用。
     */
    private static final String PARTITIONER_ADPATIVE_PARTITIONING_ENABLE_DOC =
            "When set to 'true', the producer will try to adapt to broker performance and produce more messages to partitions hosted on faster brokers. "
            + "If 'false', producer will try to distribute messages uniformly. Note: this setting has no effect if a custom partitioner is used";

    // ----------------------------------------------------------------

    /** <code>partitioner.availability.timeout.ms</code> */
    public static final String PARTITIONER_AVAILABILITY_TIMEOUT_MS_CONFIG = "partitioner.availability.timeout.ms";
    /**
     * If a broker cannot process produce requests from a partition for <code>partitioner.availability.timeout.ms</code> time,
     * the partitioner treats that partition as not available.
     * If the value is 0, this logic is disabled.
     * Note: this setting has no effect if a custom partitioner is used or <code>partitioner.adaptive.partitioning.enable</code> is set to 'false'
     */
    private static final String PARTITIONER_AVAILABILITY_TIMEOUT_MS_DOC =
            "If a broker cannot process produce requests from a partition for <code>" + PARTITIONER_AVAILABILITY_TIMEOUT_MS_CONFIG + "</code> time, "
            + "the partitioner treats that partition as not available.  If the value is 0, this logic is disabled. "
            + "Note: this setting has no effect if a custom partitioner is used or <code>" + PARTITIONER_ADPATIVE_PARTITIONING_ENABLE_CONFIG
            + "</code> is set to 'false'";

    // ----------------------------------------------------------------

    /** <code>partitioner.ignore.keys</code> */
    public static final String PARTITIONER_IGNORE_KEYS_CONFIG = "partitioner.ignore.keys";
    /**
     * When set to 'true' the producer won't use record keys to choose a partition. If 'false', producer would choose a partition based on a hash of the key when a key is present. Note: this setting has no effect if a custom partitioner is used.
     */
    private static final String PARTITIONER_IGNORE_KEYS_DOC = "When set to 'true' the producer won't use record keys to choose a partition. "
            + "If 'false', producer would choose a partition based on a hash of the key when a key is present. "
            + "Note: this setting has no effect if a custom partitioner is used.";

    // ----------------------------------------------------------------

    /** <code>acks</code> */
    public static final String ACKS_CONFIG = "acks";
    /**
     * The number of acknowledgments the producer requires the leader to have received before considering a request complete.
     * This controls the  durability of records that are sent.
     * The following settings are allowed:
     * <ul>
     *     <li><code>acks=0</code>
     *     If set to zero then the producer will not wait for any acknowledgment from the server at all.
     *     The record will be immediately added to the socket buffer and considered sent.
     *     No guarantee can be made that the server has received the record in this case,
     *     and the <code>retries</code> configuration will not take effect (as the client won't generally know of any failures).
     *     The offset given back for each record will always be set to <code>-1</code>.
     *     <li><code>acks=1</code>
     *     This will mean the leader will write the record to its local log but will respond without awaiting full acknowledgement from all followers.
     *     In this case should the leader fail immediately after acknowledging the record but before the followers have replicated it then the record will be lost.
     *     <li><code>acks=all</code>
     *     This means the leader will wait for the full set of in-sync replicas to acknowledge the record.
     *     This guarantees that the record will not be lost as long as at least one in-sync replica remains alive.
     *     This is the strongest available guarantee. This is equivalent to the acks=-1 setting.
     * </ul>
     * <p>
     * Note that enabling idempotence requires this config value to be 'all'.
     * If conflicting configurations are set and idempotence is not explicitly enabled, idempotence is disabled.
     */
    /*
     生产者要求 leader 节点在确认请求完成前必须收到的应答数量。
     该参数控制发送记录的持久性保证级别。可选配置如下：
     <ul>
     <li><code>acks=0</code>：
     设置为零时，生产者完全不会等待服务器的任何确认应答。
     记录会立即加入套接字缓冲区并视为已发送。
     这种情况下无法保证服务器是否已收到记录，
     且<code>retries</code>配置将不会生效（因为客户端通常无法感知任何失败）。
     每条记录返回的 offset 始终为<code>-1</code>。
     <li><code>acks=1</code>：
     表示 leader 会将记录写入本地日志，但无需等待所有 follower 节点的完全确认即可响应。
     若 leader 在确认记录后、follower 完成复制前立即故障，则该记录会丢失。
     <li><code>acks=all</code>：
     表示 leader 将等待所有同步副本（in-sync replicas）确认记录。
     这能确保只要至少一个同步副本存活，记录就不会丢失。
     这是最高等级的持久性保证，等效于 acks=-1 设置。
     </ul>
     <p>
     注意：启用幂等性(idempotence)必须将此参数设为'all'。
     如果存在配置冲突且未显式启用幂等性，则幂等性功能将被禁用。
     */
    // 这个参数用来指定分区中必须要有多少个副本收到这条消息，之后生产者才会认为这条消息是成功写入的。
    // acks 是生产者客户端中一个非常重要的参数，它涉及消息的可靠性和吞吐量之间的权衡。　　
    // acks 参数有 3 种类型的值（都是字符串类型）。
    // acks =1 : 默认值即为l 。生产者发送消息之后，只要分区的leader 副本成功写入消息，那么它就会收到来自服务端的成功响应 。
    //     如果消息无法写入 leader 副本，比如在leader 副本崩溃、重新选举新的leader 副本的过程中，那么生产者就会收到一个错误的响应，
    //     为了避免消息丢失，生产者可以选择重发消息 。
    //     如果消息写入 leader 副本并返回成功响应给生产者，且在被其他 follower 副本拉取之前 leader 副本崩溃，那么此时消息还是会丢失，
    //     因为新选举的 leader 副本中并没有这条对应的消息 。
    //     acks 设置为1 ，是消息可靠性和吞吐量之间的折中方案。
    // acks = 0 :生产者发送消 息之后不需要等待任何服务端的响应。
    //     如果在消息从发送到写入 Kafka 的过程中出现某些异常，导致 Kafka 并没有收到这条消息，那么生产者也无从得知，消息也就丢失了。
    //     在其他配置环境相同的情况下，acks 设置为 0 可以达到最大的吞吐量。
    // acks ＝- l 或 acks =all : 生产者在消 息发送之后，需要等待 ISR 中的所有副本都成功写入消息之后才能够收到来自服务端的成功响应。
    //     在其他配置环境相同的情况下，acks 设置为-1(all ）可以达到最强的可靠性。
    //     但这并不意味着消息就一定可靠，因为 ISR 中可能只有 leader 副本，这样就退化成了 acks= l 的情况。
    private static final String ACKS_DOC = "The number of acknowledgments the producer requires the leader to have received before considering a request complete. This controls the "
                                           + " durability of records that are sent. The following settings are allowed: "
                                           + " <ul>"
                                           + " <li><code>acks=0</code> If set to zero then the producer will not wait for any acknowledgment from the"
                                           + " server at all. The record will be immediately added to the socket buffer and considered sent. No guarantee can be"
                                           + " made that the server has received the record in this case, and the <code>retries</code> configuration will not"
                                           + " take effect (as the client won't generally know of any failures). The offset given back for each record will"
                                           + " always be set to <code>-1</code>."
                                           + " <li><code>acks=1</code> This will mean the leader will write the record to its local log but will respond"
                                           + " without awaiting full acknowledgement from all followers. In this case should the leader fail immediately after"
                                           + " acknowledging the record but before the followers have replicated it then the record will be lost."
                                           + " <li><code>acks=all</code> This means the leader will wait for the full set of in-sync replicas to"
                                           + " acknowledge the record. This guarantees that the record will not be lost as long as at least one in-sync replica"
                                           + " remains alive. This is the strongest available guarantee. This is equivalent to the acks=-1 setting."
                                           + "</ul>"
                                           + "<p>"
                                           + "Note that enabling idempotence requires this config value to be 'all'."
                                           + " If conflicting configurations are set and idempotence is not explicitly enabled, idempotence is disabled.";

    // ----------------------------------------------------------------

    /** <code>linger.ms</code> */
    public static final String LINGER_MS_CONFIG = "linger.ms";
    /**
     * The producer groups together any records that arrive in between request transmissions into a single batched request.
     * Normally this occurs only under load when records arrive faster than they can be sent out.
     * However in some circumstances the client may want to reduce the number of requests even under moderate load.
     * This setting accomplishes this by adding a small amount of artificial delay&mdash;that is, rather than immediately sending out a record,
     * the producer will wait for up to the given delay to allow other records to be sent so that the sends can be batched together.
     * This can be thought of as analogous to Nagle's algorithm in TCP. This setting gives the upper bound on the delay for batching:
     * once we get <code>batch.size</code> worth of records for a partition it will be sent immediately regardless of this setting,
     * however if we have fewer than this many bytes accumulated for this partition we will 'linger' for the specified time waiting for more records to show up.
     * This setting defaults to 0 (i.e. no delay). Setting <code>linger.ms=5</code>,
     * for example, would have the effect of reducing the number of requests sent but would add up to 5ms of latency to records sent in the absence of load.
     */
    /*
     生产者会将两次请求传输之间到达的所有记录合并为一个批量请求。
     通常情况下，这只有在高负载时（记录到达速度超过发送速度）才会发生。
     但在某些情况下，客户端可能希望即使在中等负载下也减少请求数量。
     该配置通过添加少量人为延迟来实现此目的——即生产者不会立即发送记录，
     而是等待给定延迟时间，以便累积其他记录形成批量发送。
     这可以类比TCP协议中的Nagle算法。该配置设定了批量等待延迟的上限：
     当一个分区的记录达到<code>batch.size</code>大小时会立即发送（无论此设置为何值），
     但如果该分区累积的字节数不足，生产者将"停留"等待更多记录到达，直到超过指定时间。
     默认值为0（即无延迟）。例如设置<code>linger.ms=5</code>，
     虽能有效减少请求发送次数，但在无负载情况下会增加最多5ms的发送延迟。
     */
    private static final String LINGER_MS_DOC = "The producer groups together any records that arrive in between request transmissions into a single batched request. "
                                                + "Normally this occurs only under load when records arrive faster than they can be sent out. However in some circumstances the client may want to "
                                                + "reduce the number of requests even under moderate load. This setting accomplishes this by adding a small amount "
                                                + "of artificial delay&mdash;that is, rather than immediately sending out a record, the producer will wait for up to "
                                                + "the given delay to allow other records to be sent so that the sends can be batched together. This can be thought "
                                                + "of as analogous to Nagle's algorithm in TCP. This setting gives the upper bound on the delay for batching: once "
                                                + "we get <code>" + BATCH_SIZE_CONFIG + "</code> worth of records for a partition it will be sent immediately regardless of this "
                                                + "setting, however if we have fewer than this many bytes accumulated for this partition we will 'linger' for the "
                                                + "specified time waiting for more records to show up. This setting defaults to 0 (i.e. no delay). Setting <code>" + LINGER_MS_CONFIG + "=5</code>, "
                                                + "for example, would have the effect of reducing the number of requests sent but would add up to 5ms of latency to records sent in the absence of load.";

    // ----------------------------------------------------------------

    /** <code>request.timeout.ms</code> */
    public static final String REQUEST_TIMEOUT_MS_CONFIG = CommonClientConfigs.REQUEST_TIMEOUT_MS_CONFIG;
    /**
     * The configuration controls the maximum amount of time the client will wait for the response of a request.
     * If the response is not received before the timeout elapses the client will resend the request if necessary
     * or fail the request if retries are exhausted.
     * This should be larger than <code>replica.lag.time.max.ms</code> (a broker configuration) to reduce
     * the possibility of message duplication due to unnecessary producer retries.
     */
    // 这个参数用来配置 Producer 等待请求响应的最长时间，默认值为 3 0000( ms ）。
    // 请求超时之后可以选择进行重试。注意这个参数需要 比 broker 端参数 replica.lag.time.max.ms 的值要大 ，这样可以减少因客户端重试而引起的消息重复的概率。
    private static final String REQUEST_TIMEOUT_MS_DOC = CommonClientConfigs.REQUEST_TIMEOUT_MS_DOC
        + " This should be larger than <code>replica.lag.time.max.ms</code> (a broker configuration)"
        + " to reduce the possibility of message duplication due to unnecessary producer retries.";

    // ----------------------------------------------------------------

    /** <code>delivery.timeout.ms</code> */
    public static final String DELIVERY_TIMEOUT_MS_CONFIG = "delivery.timeout.ms";
    /**
     * An upper bound on the time to report success or failure after a call to <code>send()</code> returns.
     * This limits the total time that a record will be delayed prior to sending,
     * the time to await acknowledgement from the broker (if expected), and the time allowed for retriable send failures.
     * The producer may report failure to send a record earlier than this config if either an unrecoverable error is encountered,
     * the retries have been exhausted, or the record is added to a batch which reached an earlier delivery expiration deadline.
     * The value of this config should be greater than or equal to the sum of <code>request.timeout.ms</code> and <code>linger.ms</code>.
     */
    /*
     * 调用<code>send()</code>方法后，报告成功或失败的最长等待时间上限。
     * 此参数限制了记录在发送前的总延迟时间、等待broker确认的时间（如果需要）以及可重试发送失败所允许的时间。
     * 如果遇到不可恢复的错误、重试次数已用尽，或记录被加入已达到更早交付截止时间的批次，
     * 生产者可能会早于此配置的时间就报告发送失败。
     * 该配置值应大于或等于<code>request.timeout.ms</code>和<code>linger.ms</code>之和。
     */
    private static final String DELIVERY_TIMEOUT_MS_DOC = "An upper bound on the time to report success or failure "
            + "after a call to <code>send()</code> returns. This limits the total time that a record will be delayed "
            + "prior to sending, the time to await acknowledgement from the broker (if expected), and the time allowed "
            + "for retriable send failures. The producer may report failure to send a record earlier than this config if "
            + "either an unrecoverable error is encountered, the retries have been exhausted, "
            + "or the record is added to a batch which reached an earlier delivery expiration deadline. "
            + "The value of this config should be greater than or equal to the sum of <code>" + REQUEST_TIMEOUT_MS_CONFIG + "</code> "
            + "and <code>" + LINGER_MS_CONFIG + "</code>.";

    // ----------------------------------------------------------------

    /** <code>client.id</code> */
    public static final String CLIENT_ID_CONFIG = CommonClientConfigs.CLIENT_ID_CONFIG;

    // ----------------------------------------------------------------

    /** <code>send.buffer.bytes</code> */
    public static final String SEND_BUFFER_CONFIG = CommonClientConfigs.SEND_BUFFER_CONFIG;

    // ----------------------------------------------------------------

    /** <code>receive.buffer.bytes</code> */
    public static final String RECEIVE_BUFFER_CONFIG = CommonClientConfigs.RECEIVE_BUFFER_CONFIG;

    // ----------------------------------------------------------------

    /** <code>max.request.size</code> */
    public static final String MAX_REQUEST_SIZE_CONFIG = "max.request.size";
    /**
     * The maximum size of a request in bytes.
     * This setting will limit the number of record batches the producer will send in a single request to avoid sending huge requests.
     * This is also effectively a cap on the maximum uncompressed record batch size.
     * Note that the server has its own cap on the record batch size (after compression if compression is enabled) which may be different from this.
     */
    // 这个参数用来限制生产者客户端能发送的消息的最大值，默认值为1048576B ，即1MB 。一般情况下，这个默认值就可以满足大多数的应用场景了。
    private static final String MAX_REQUEST_SIZE_DOC =
        "The maximum size of a request in bytes. This setting will limit the number of record " +
        "batches the producer will send in a single request to avoid sending huge requests. " +
        "This is also effectively a cap on the maximum uncompressed record batch size. Note that the server " +
        "has its own cap on the record batch size (after compression if compression is enabled) which may be different from this.";

    // ----------------------------------------------------------------

    /** <code>reconnect.backoff.ms</code> */
    public static final String RECONNECT_BACKOFF_MS_CONFIG = CommonClientConfigs.RECONNECT_BACKOFF_MS_CONFIG;

    // ----------------------------------------------------------------

    /** <code>reconnect.backoff.max.ms</code> */
    public static final String RECONNECT_BACKOFF_MAX_MS_CONFIG = CommonClientConfigs.RECONNECT_BACKOFF_MAX_MS_CONFIG;

    // ----------------------------------------------------------------

    /** <code>max.block.ms</code> */
    public static final String MAX_BLOCK_MS_CONFIG = "max.block.ms";
    /**
     * The configuration controls how long the <code>KafkaProducer</code>'s <code>send()</code>, <code>partitionsFor()</code>,
     * <code>initTransactions()</code>, <code>sendOffsetsToTransaction()</code>, <code>commitTransaction()</code>
     * and <code>abortTransaction()</code> methods will block.
     * For <code>send()</code> this timeout bounds the total time waiting for both metadata fetch and buffer allocation
     * (blocking in the user-supplied serializers or partitioner is not counted against this timeout).
     * For <code>partitionsFor()</code> this timeout bounds the time spent waiting for metadata if it is unavailable.
     * The transaction-related methods always block, but may timeout if the transaction coordinator
     * could not be discovered or did not respond within the timeout.
     */
    private static final String MAX_BLOCK_MS_DOC = "The configuration controls how long the <code>KafkaProducer</code>'s <code>send()</code>, <code>partitionsFor()</code>, "
                                                    + "<code>initTransactions()</code>, <code>sendOffsetsToTransaction()</code>, <code>commitTransaction()</code> "
                                                    + "and <code>abortTransaction()</code> methods will block. "
                                                    + "For <code>send()</code> this timeout bounds the total time waiting for both metadata fetch and buffer allocation "
                                                    + "(blocking in the user-supplied serializers or partitioner is not counted against this timeout). "
                                                    + "For <code>partitionsFor()</code> this timeout bounds the time spent waiting for metadata if it is unavailable. "
                                                    + "The transaction-related methods always block, but may timeout if "
                                                    + "the transaction coordinator could not be discovered or did not respond within the timeout.";

    // ----------------------------------------------------------------

    /** <code>buffer.memory</code> */
    public static final String BUFFER_MEMORY_CONFIG = "buffer.memory";
    /**
     * The total bytes of memory the producer can use to buffer records waiting to be sent to the server. If records are sent faster than they can be delivered to the server the producer will block for <code>max.block.ms</code> after which it will throw an exception.<p>This setting should correspond roughly to the total memory the producer will use, but is not a hard bound since not all memory the producer uses is used for buffering. Some additional memory will be used for compression (if compression is enabled) as well as for maintaining in-flight requests.
     */
    private static final String BUFFER_MEMORY_DOC = "The total bytes of memory the producer can use to buffer records waiting to be sent to the server. If records are "
                                                    + "sent faster than they can be delivered to the server the producer will block for <code>" + MAX_BLOCK_MS_CONFIG + "</code> after which it will throw an exception."
                                                    + "<p>"
                                                    + "This setting should correspond roughly to the total memory the producer will use, but is not a hard bound since "
                                                    + "not all memory the producer uses is used for buffering. Some additional memory will be used for compression (if "
                                                    + "compression is enabled) as well as for maintaining in-flight requests.";

    // ----------------------------------------------------------------

    /** <code>retry.backoff.ms</code> */
    public static final String RETRY_BACKOFF_MS_CONFIG = CommonClientConfigs.RETRY_BACKOFF_MS_CONFIG;

    // ----------------------------------------------------------------

    /** <code>compression.type</code> */
    public static final String COMPRESSION_TYPE_CONFIG = "compression.type";
    /**
     * The compression type for all data generated by the producer.
     * The default is none (i.e. no compression).
     * Valid  values are <code>none</code>, <code>gzip</code>, <code>snappy</code>, <code>lz4</code>, or <code>zstd</code>.
     * Compression is of full batches of data,
     * so the efficacy of batching will also impact the compression ratio (more batching means better compression).
     */
    /*
     * 生产者生成数据所使用的压缩类型。
     * 默认值为 none（即不压缩）。
     * 有效取值包括：<code>none</code>、<code>gzip</code>、<code>snappy</code>、<code>lz4</code> 或 <code>zstd</code>。
     * 压缩是针对整批数据进行的，
     * 因此批处理效率也会影响压缩率（批处理量越大通常压缩效果越好）。
     */
    // 这个参数用来指定消息的压缩方式，默认值为“ none ”，即默认情况下，消息不会被压缩。
    // 该参数还可以配置为“ gzip ”,“ snappy ” 和“ lz4 ”。
    // 对消息进行压缩可以极大地减少网络传输量 、降低网络 IO ，从而提高整体的性能 。
    // 消息压缩是一种使用时间换空间的优化方式，如果对时延有一定的要求，则不推荐对消息进行压缩 。
    private static final String COMPRESSION_TYPE_DOC = "The compression type for all data generated by the producer. The default is none (i.e. no compression). Valid "
                                                       + " values are <code>none</code>, <code>gzip</code>, <code>snappy</code>, <code>lz4</code>, or <code>zstd</code>. "
                                                       + "Compression is of full batches of data, so the efficacy of batching will also impact the compression ratio (more batching means better compression).";

    // ----------------------------------------------------------------

    /** <code>metrics.sample.window.ms</code> */
    public static final String METRICS_SAMPLE_WINDOW_MS_CONFIG = CommonClientConfigs.METRICS_SAMPLE_WINDOW_MS_CONFIG;

    // ----------------------------------------------------------------

    /** <code>metrics.num.samples</code> */
    public static final String METRICS_NUM_SAMPLES_CONFIG = CommonClientConfigs.METRICS_NUM_SAMPLES_CONFIG;

    // ----------------------------------------------------------------

    /**
     * <code>metrics.recording.level</code>
     */
    public static final String METRICS_RECORDING_LEVEL_CONFIG = CommonClientConfigs.METRICS_RECORDING_LEVEL_CONFIG;

    // ----------------------------------------------------------------

    /** <code>metric.reporters</code> */
    public static final String METRIC_REPORTER_CLASSES_CONFIG = CommonClientConfigs.METRIC_REPORTER_CLASSES_CONFIG;

    // ----------------------------------------------------------------

    /** <code>auto.include.jmx.reporter</code> */
    @Deprecated
    public static final String AUTO_INCLUDE_JMX_REPORTER_CONFIG = CommonClientConfigs.AUTO_INCLUDE_JMX_REPORTER_CONFIG;

    // max.in.flight.requests.per.connection should be less than or equal to 5 when idempotence producer enabled to ensure message ordering
    private static final int MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_FOR_IDEMPOTENCE = 5;

    // ----------------------------------------------------------------

    /** <code>max.in.flight.requests.per.connection</code> */
    public static final String MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION = "max.in.flight.requests.per.connection";
    /**
     * The maximum number of unacknowledged requests the client will send on a single connection before blocking. Note that if this configuration is set to be greater than 1 and <code>enable.idempotence</code> is set to false, there is a risk of message reordering after a failed send due to retries (i.e., if retries are enabled);  if retries are disabled or if <code>enable.idempotence</code> is set to true, ordering will be preserved. Additionally, enabling idempotence requires the value of this configuration to be less than or equal to 5. If conflicting configurations are set and idempotence is not explicitly enabled, idempotence is disabled.
     */
    private static final String MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_DOC = "The maximum number of unacknowledged requests the client will send on a single connection before blocking."
                                                                            + " Note that if this configuration is set to be greater than 1 and <code>enable.idempotence</code> is set to false, there is a risk of"
                                                                            + " message reordering after a failed send due to retries (i.e., if retries are enabled); "
                                                                            + " if retries are disabled or if <code>enable.idempotence</code> is set to true, ordering will be preserved."
                                                                            + " Additionally, enabling idempotence requires the value of this configuration to be less than or equal to " + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_FOR_IDEMPOTENCE + "."
                                                                            + " If conflicting configurations are set and idempotence is not explicitly enabled, idempotence is disabled. ";

    // ----------------------------------------------------------------

    /** <code>retries</code> */
    public static final String RETRIES_CONFIG = CommonClientConfigs.RETRIES_CONFIG;
    /**
     * Setting a value greater than zero will cause the client to resend any record whose send fails with a potentially transient error.
     * Note that this retry is no different than if the client resent the record upon receiving the error.
     * Produce requests will be failed before the number of retries has been exhausted if the timeout configured by
     * <code>delivery.timeout.ms</code> expires first before successful acknowledgement.
     * Users should generally prefer to leave this config unset and instead use <code>delivery.timeout.ms</code> to control retry behavior.
     * <p>
     *     Enabling idempotence requires this config value to be greater than 0.
     *     If conflicting configurations are set and idempotence is not explicitly enabled, idempotence is disabled.
     * <p>
     * Allowing retries while setting <code>enable.idempotence</code> to <code>false</code> and
     * <code>max.in.flight.requests.per.connection</code> to 1 will potentially change the ordering of records because if two batches are sent to a single partition,
     * and the first fails and is retried but the second succeeds,
     * then the records in the second batch may appear first.
     */
    /*
     * 设置大于零的值将使客户端重新发送任何因可能暂时性错误而发送失败的记录。
     * 请注意，这种重试与客户端在收到错误后重新发送记录的行为并无不同。
     * 如果在达到重试次数上限之前，由<code>delivery.timeout.ms</code>配置的超时时间已到期且未收到成功确认，
     * 则生产请求将被判定为失败。通常建议用户不要设置此参数，而改用<code>delivery.timeout.ms</code>来控制重试行为。
     * <p>
     * 启用幂等性要求此配置值必须大于0。如果设置了冲突的配置且未显式启用幂等性，则幂等性将被禁用。
     * <p>
     * 当<code>enable.idempotence</code>设为<code>false</code>且<code>max.in.flight.requests.per.connection</code>
     * 设为1时允许重试，可能会改变记录的顺序。因为如果两个批次被发送到同一分区，
     * 且第一个批次失败并重试，而第二个批次成功，那么第二个批次中的记录可能会先出现。
     */
    //  retries 参数用来配置生产者重试的次数，默认值为 0，即在发生异常的时候不进行任何重试动作。
    //  消息在从生产者发出到成功写入服务器之前可能发生一些临时性的异常，比如网络抖动、leader 副本的选举等，这种异常往往是可以自行恢复的，
    //  生产者可以通过配置 retries大于 0 的值，以此通过 内 部重试来恢复而不是一昧地将异常抛给生产者的应用程序。
    //  如果重试达到设定的次数 ，那么生产者就会放弃重试并返回异常。
    // 　不过并不是所有的异常都是可以通过重试来解决的，比如消息太大，超过 max.request.size 参数配置的值时，这种方式就不可行了。
    // 重试还和另一个参数 retry.backoff.ms 有关，这个参数的默认值为100 ，它用来设定两次重试之间的时间间隔，避免无效的频繁重试。
    // 在配置 retries 和retry.backoff.ms之前，最好先估算一下可能的异常恢复时间，这样可以设定总的重试时间大于这个异常恢复时间，以此来避免生产者过早地放弃重试 。
    private static final String RETRIES_DOC = "Setting a value greater than zero will cause the client to resend any record whose send fails with a potentially transient error."
            + " Note that this retry is no different than if the client resent the record upon receiving the error."
            + " Produce requests will be failed before the number of retries has been exhausted if the timeout configured by"
            + " <code>" + DELIVERY_TIMEOUT_MS_CONFIG + "</code> expires first before successful acknowledgement. Users should generally"
            + " prefer to leave this config unset and instead use <code>" + DELIVERY_TIMEOUT_MS_CONFIG + "</code> to control"
            + " retry behavior."
            + "<p>"
            + "Enabling idempotence requires this config value to be greater than 0."
            + " If conflicting configurations are set and idempotence is not explicitly enabled, idempotence is disabled."
            + "<p>"
            + "Allowing retries while setting <code>enable.idempotence</code> to <code>false</code> and <code>" + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION + "</code> to 1 will potentially change the"
            + " ordering of records because if two batches are sent to a single partition, and the first fails and is retried but the second"
            + " succeeds, then the records in the second batch may appear first.";

    // ----------------------------------------------------------------

    /** <code>key.serializer</code> */
    public static final String KEY_SERIALIZER_CLASS_CONFIG = "key.serializer";
    /**
     * Serializer class for key that implements the <code>org.apache.kafka.common.serialization.Serializer</code> interface.
     */
    public static final String KEY_SERIALIZER_CLASS_DOC = "Serializer class for key that implements the <code>org.apache.kafka.common.serialization.Serializer</code> interface.";

    // ----------------------------------------------------------------

    /** <code>value.serializer</code> */
    public static final String VALUE_SERIALIZER_CLASS_CONFIG = "value.serializer";
    /**
     * Serializer class for value that implements the <code>org.apache.kafka.common.serialization.Serializer</code> interface.
     */
    public static final String VALUE_SERIALIZER_CLASS_DOC = "Serializer class for value that implements the <code>org.apache.kafka.common.serialization.Serializer</code> interface.";

    // ----------------------------------------------------------------

    /** <code>socket.connection.setup.timeout.ms</code> */
    public static final String SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG = CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG;

    // ----------------------------------------------------------------

    /** <code>socket.connection.setup.timeout.max.ms</code> */
    public static final String SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG = CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG;

    // ----------------------------------------------------------------

    /** <code>connections.max.idle.ms</code> */
    public static final String CONNECTIONS_MAX_IDLE_MS_CONFIG = CommonClientConfigs.CONNECTIONS_MAX_IDLE_MS_CONFIG;

    // ----------------------------------------------------------------

    /** <code>partitioner.class</code> */
    public static final String PARTITIONER_CLASS_CONFIG = "partitioner.class";
    /**
     * A class to use to determine which partition to be send to when produce the records. Available options are:<ul><li>If not set, the default partitioning logic is used. This strategy will try sticking to a partition until at least batch.size bytes is produced to the partition. It works with the strategy:<ul><li>If no partition is specified but a key is present, choose a partition based on a hash of the key</li><li>If no partition or key is present, choose the sticky partition that changes when at least batch.size bytes are produced to the partition.</li></ul></li><li><code>org.apache.kafka.clients.producer.RoundRobinPartitioner</code>: This partitioning strategy is that each record in a series of consecutive records will be sent to a different partition(no matter if the 'key' is provided or not), until we run out of partitions and start over again. Note: There's a known issue that will cause uneven distribution when new batch is created. Please check KAFKA-9965 for more detail.</li></ul><p>Implementing the <code>org.apache.kafka.clients.producer.Partitioner</code> interface allows you to plug in a custom partitioner.
     */
    private static final String PARTITIONER_CLASS_DOC = "A class to use to determine which partition to be send to when produce the records. Available options are:" +
        "<ul>" +
            "<li>If not set, the default partitioning logic is used. " +
        "This strategy will try sticking to a partition until at least " + BATCH_SIZE_CONFIG + " bytes is produced to the partition. It works with the strategy:" +
                "<ul>" +
                    "<li>If no partition is specified but a key is present, choose a partition based on a hash of the key</li>" +
                    "<li>If no partition or key is present, choose the sticky partition that changes when at least " + BATCH_SIZE_CONFIG + " bytes are produced to the partition.</li>" +
                "</ul>" +
            "</li>" +
            "<li><code>org.apache.kafka.clients.producer.RoundRobinPartitioner</code>: This partitioning strategy is that " +
        "each record in a series of consecutive records will be sent to a different partition(no matter if the 'key' is provided or not), " +
        "until we run out of partitions and start over again. Note: There's a known issue that will cause uneven distribution when new batch is created. " +
        "Please check KAFKA-9965 for more detail." +
            "</li>" +
        "</ul>" +
        "<p>Implementing the <code>org.apache.kafka.clients.producer.Partitioner</code> interface allows you to plug in a custom partitioner.";

    // ----------------------------------------------------------------

    /** <code>interceptor.classes</code> */
    public static final String INTERCEPTOR_CLASSES_CONFIG = "interceptor.classes";
    /**
     * A list of classes to use as interceptors. Implementing the <code>org.apache.kafka.clients.producer.ProducerInterceptor</code> interface allows you to intercept (and possibly mutate) the records received by the producer before they are published to the Kafka cluster. By default, there are no interceptors.
     */
    public static final String INTERCEPTOR_CLASSES_DOC = "A list of classes to use as interceptors. "
                                                        + "Implementing the <code>org.apache.kafka.clients.producer.ProducerInterceptor</code> interface allows you to intercept (and possibly mutate) the records "
                                                        + "received by the producer before they are published to the Kafka cluster. By default, there are no interceptors.";

    // ----------------------------------------------------------------

    /** <code>enable.idempotence</code> */
    public static final String ENABLE_IDEMPOTENCE_CONFIG = "enable.idempotence";
    /**
     * When set to 'true', the producer will ensure that exactly one copy of each message is written in the stream.
     * If 'false', producer retries due to broker failures, etc., may write duplicates of the retried message in the stream.
     * Note that enabling idempotence requires <code>max.in.flight.requests.per.connection</code> to be less than or equal to 5 (with message ordering preserved for any allowable value),
     * <code>retries</code> to be greater than 0, and <code>acks</code> must be 'all'.
     * <p>Idempotence is enabled by default if no conflicting configurations are set.
     * If conflicting configurations are set and idempotence is not explicitly enabled, idempotence is disabled.
     * If idempotence is explicitly enabled and conflicting configurations are set, a <code>ConfigException</code> is thrown.
     */
    /*
     当设为'true'时，生产者将确保每条消息在数据流中仅被写入一次。
     若设为'false'，由于broker故障等原因导致的重试可能会在流中产生重复消息。
     注意：启用幂等性(idempotence)需满足以下条件：
     <code>max.in.flight.requests.per.connection</code> ≤ 5（且任何允许值下都保持消息顺序），
     <code>retries</code> > 0，
     且<code>acks</code>必须设为'all'。
     <p>若未设置冲突配置，默认将启用幂等性。
     如果存在配置冲突且未显式启用幂等性，则自动禁用该功能。
     若显式启用幂等性但存在冲突配置，将抛出<code>ConfigException</code>异常。
     */
    public static final String ENABLE_IDEMPOTENCE_DOC = "When set to 'true', the producer will ensure that exactly one copy of each message is written in the stream. If 'false', producer "
                                                        + "retries due to broker failures, etc., may write duplicates of the retried message in the stream. "
                                                        + "Note that enabling idempotence requires <code>" + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION + "</code> to be less than or equal to " + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_FOR_IDEMPOTENCE
                                                        + " (with message ordering preserved for any allowable value), <code>" + RETRIES_CONFIG + "</code> to be greater than 0, and <code>"
                                                        + ACKS_CONFIG + "</code> must be 'all'. "
                                                        + "<p>"
                                                        + "Idempotence is enabled by default if no conflicting configurations are set. "
                                                        + "If conflicting configurations are set and idempotence is not explicitly enabled, idempotence is disabled. "
                                                        + "If idempotence is explicitly enabled and conflicting configurations are set, a <code>ConfigException</code> is thrown.";

    // ----------------------------------------------------------------

    /** <code> transaction.timeout.ms </code> */
    public static final String TRANSACTION_TIMEOUT_CONFIG = "transaction.timeout.ms";
    /**
     * The maximum amount of time in ms that the transaction coordinator will wait for a transaction status update from the producer before proactively aborting the ongoing transaction.If this value is larger than the transaction.max.timeout.ms setting in the broker, the request will fail with a <code>InvalidTxnTimeoutException</code> error.
     */
    public static final String TRANSACTION_TIMEOUT_DOC = "The maximum amount of time in ms that the transaction coordinator will wait for a transaction status update from the producer before proactively aborting the ongoing transaction." +
            "If this value is larger than the transaction.max.timeout.ms setting in the broker, the request will fail with a <code>InvalidTxnTimeoutException</code> error.";

    // ----------------------------------------------------------------

    /** <code> transactional.id </code> */
    public static final String TRANSACTIONAL_ID_CONFIG = "transactional.id";
    /**
     * The TransactionalId to use for transactional delivery.
     * This enables reliability semantics which span multiple producer sessions since it allows the client
     * to guarantee that transactions using the same TransactionalId have been completed prior to starting any new transactions.
     * If no TransactionalId is provided, then the producer is limited to idempotent delivery.
     * If a TransactionalId is configured, <code>enable.idempotence</code> is implied.
     * By default the TransactionId is not configured, which means transactions cannot be used.
     * Note that, by default, transactions require a cluster of at least three brokers which is the recommended setting for production;
     * for development you can change this, by adjusting broker setting <code>transaction.state.log.replication.factor</code>.
     */
    public static final String TRANSACTIONAL_ID_DOC = "The TransactionalId to use for transactional delivery. This enables reliability semantics which span multiple producer sessions since it allows the client to guarantee that transactions using the same TransactionalId have been completed prior to starting any new transactions. If no TransactionalId is provided, then the producer is limited to idempotent delivery. " +
            "If a TransactionalId is configured, <code>enable.idempotence</code> is implied. " +
            "By default the TransactionId is not configured, which means transactions cannot be used. " +
            "Note that, by default, transactions require a cluster of at least three brokers which is the recommended setting for production; for development you can change this, by adjusting broker setting <code>transaction.state.log.replication.factor</code>.";

    // ----------------------------------------------------------------

    /**
     * <code>security.providers</code>
     */
    public static final String SECURITY_PROVIDERS_CONFIG = SecurityConfig.SECURITY_PROVIDERS_CONFIG;
    private static final String SECURITY_PROVIDERS_DOC = SecurityConfig.SECURITY_PROVIDERS_DOC;

    // ----------------------------------------------------------------

    private static final AtomicInteger PRODUCER_CLIENT_ID_SEQUENCE = new AtomicInteger(1);

    static {
        // 给所有的参数设置默认值等信息
        CONFIG = new ConfigDef().define(BOOTSTRAP_SERVERS_CONFIG, Type.LIST, Collections.emptyList(), new ConfigDef.NonNullValidator(), Importance.HIGH, CommonClientConfigs.BOOTSTRAP_SERVERS_DOC)
                                .define(CLIENT_DNS_LOOKUP_CONFIG,
                                        Type.STRING,
                                        ClientDnsLookup.USE_ALL_DNS_IPS.toString(),
                                        in(ClientDnsLookup.USE_ALL_DNS_IPS.toString(),
                                           ClientDnsLookup.RESOLVE_CANONICAL_BOOTSTRAP_SERVERS_ONLY.toString()),
                                        Importance.MEDIUM,
                                        CommonClientConfigs.CLIENT_DNS_LOOKUP_DOC)
                                .define(BUFFER_MEMORY_CONFIG, Type.LONG, 32 * 1024 * 1024L, atLeast(0L), Importance.HIGH, BUFFER_MEMORY_DOC)
                                .define(RETRIES_CONFIG, Type.INT, Integer.MAX_VALUE, between(0, Integer.MAX_VALUE), Importance.HIGH, RETRIES_DOC)
                                .define(ACKS_CONFIG,
                                        Type.STRING,
                                        "all",
                                        in("all", "-1", "0", "1"),
                                        Importance.LOW,
                                        ACKS_DOC)
                                .define(COMPRESSION_TYPE_CONFIG, Type.STRING, CompressionType.NONE.name, in(Utils.enumOptions(CompressionType.class)), Importance.HIGH, COMPRESSION_TYPE_DOC)
                                .define(BATCH_SIZE_CONFIG, Type.INT, 16384, atLeast(0), Importance.MEDIUM, BATCH_SIZE_DOC)
                                .define(PARTITIONER_ADPATIVE_PARTITIONING_ENABLE_CONFIG, Type.BOOLEAN, true, Importance.LOW, PARTITIONER_ADPATIVE_PARTITIONING_ENABLE_DOC)
                                .define(PARTITIONER_AVAILABILITY_TIMEOUT_MS_CONFIG, Type.LONG, 0, atLeast(0), Importance.LOW, PARTITIONER_AVAILABILITY_TIMEOUT_MS_DOC)
                                .define(PARTITIONER_IGNORE_KEYS_CONFIG, Type.BOOLEAN, false, Importance.MEDIUM, PARTITIONER_IGNORE_KEYS_DOC)
                                .define(LINGER_MS_CONFIG, Type.LONG, 0, atLeast(0), Importance.MEDIUM, LINGER_MS_DOC)
                                .define(DELIVERY_TIMEOUT_MS_CONFIG, Type.INT, 120 * 1000, atLeast(0), Importance.MEDIUM, DELIVERY_TIMEOUT_MS_DOC)
                                .define(CLIENT_ID_CONFIG, Type.STRING, "", Importance.MEDIUM, CommonClientConfigs.CLIENT_ID_DOC)
                                .define(SEND_BUFFER_CONFIG, Type.INT, 128 * 1024, atLeast(CommonClientConfigs.SEND_BUFFER_LOWER_BOUND), Importance.MEDIUM, CommonClientConfigs.SEND_BUFFER_DOC)
                                .define(RECEIVE_BUFFER_CONFIG, Type.INT, 32 * 1024, atLeast(CommonClientConfigs.RECEIVE_BUFFER_LOWER_BOUND), Importance.MEDIUM, CommonClientConfigs.RECEIVE_BUFFER_DOC)
                                .define(MAX_REQUEST_SIZE_CONFIG,
                                        Type.INT,
                                        1024 * 1024,
                                        atLeast(0),
                                        Importance.MEDIUM,
                                        MAX_REQUEST_SIZE_DOC)
                                .define(RECONNECT_BACKOFF_MS_CONFIG, Type.LONG, 50L, atLeast(0L), Importance.LOW, CommonClientConfigs.RECONNECT_BACKOFF_MS_DOC)
                                .define(RECONNECT_BACKOFF_MAX_MS_CONFIG, Type.LONG, 1000L, atLeast(0L), Importance.LOW, CommonClientConfigs.RECONNECT_BACKOFF_MAX_MS_DOC)
                                .define(RETRY_BACKOFF_MS_CONFIG, Type.LONG, 100L, atLeast(0L), Importance.LOW, CommonClientConfigs.RETRY_BACKOFF_MS_DOC)
                                .define(MAX_BLOCK_MS_CONFIG,
                                        Type.LONG,
                                        60 * 1000,
                                        atLeast(0),
                                        Importance.MEDIUM,
                                        MAX_BLOCK_MS_DOC)
                                .define(REQUEST_TIMEOUT_MS_CONFIG,
                                        Type.INT,
                                        30 * 1000,
                                        atLeast(0),
                                        Importance.MEDIUM,
                                        REQUEST_TIMEOUT_MS_DOC)
                                .define(METADATA_MAX_AGE_CONFIG, Type.LONG, 5 * 60 * 1000, atLeast(0), Importance.LOW, METADATA_MAX_AGE_DOC)
                                .define(METADATA_MAX_IDLE_CONFIG,
                                        Type.LONG,
                                        5 * 60 * 1000,
                                        atLeast(5000),
                                        Importance.LOW,
                                        METADATA_MAX_IDLE_DOC)
                                .define(METRICS_SAMPLE_WINDOW_MS_CONFIG,
                                        Type.LONG,
                                        30000,
                                        atLeast(0),
                                        Importance.LOW,
                                        CommonClientConfigs.METRICS_SAMPLE_WINDOW_MS_DOC)
                                .define(METRICS_NUM_SAMPLES_CONFIG, Type.INT, 2, atLeast(1), Importance.LOW, CommonClientConfigs.METRICS_NUM_SAMPLES_DOC)
                                .define(METRICS_RECORDING_LEVEL_CONFIG,
                                        Type.STRING,
                                        Sensor.RecordingLevel.INFO.toString(),
                                        in(Sensor.RecordingLevel.INFO.toString(), Sensor.RecordingLevel.DEBUG.toString(), Sensor.RecordingLevel.TRACE.toString()),
                                        Importance.LOW,
                                        CommonClientConfigs.METRICS_RECORDING_LEVEL_DOC)
                                .define(METRIC_REPORTER_CLASSES_CONFIG,
                                        Type.LIST,
                                        Collections.emptyList(),
                                        new ConfigDef.NonNullValidator(),
                                        Importance.LOW,
                                        CommonClientConfigs.METRIC_REPORTER_CLASSES_DOC)
                                .define(AUTO_INCLUDE_JMX_REPORTER_CONFIG,
                                        Type.BOOLEAN,
                                        true,
                                        Importance.LOW,
                                        CommonClientConfigs.AUTO_INCLUDE_JMX_REPORTER_DOC)
                                .define(MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION,
                                        Type.INT,
                                        5,
                                        atLeast(1),
                                        Importance.LOW,
                                        MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_DOC)
                                .define(KEY_SERIALIZER_CLASS_CONFIG,
                                        Type.CLASS,
                                        Importance.HIGH,
                                        KEY_SERIALIZER_CLASS_DOC)
                                .define(VALUE_SERIALIZER_CLASS_CONFIG,
                                        Type.CLASS,
                                        Importance.HIGH,
                                        VALUE_SERIALIZER_CLASS_DOC)
                                .define(SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG,
                                        Type.LONG,
                                        CommonClientConfigs.DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MS,
                                        Importance.MEDIUM,
                                        CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_DOC)
                                .define(SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG,
                                        Type.LONG,
                                        CommonClientConfigs.DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS,
                                        Importance.MEDIUM,
                                        CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_DOC)
                                /* default is set to be a bit lower than the server default (10 min), to avoid both client and server closing connection at same time */
                                .define(CONNECTIONS_MAX_IDLE_MS_CONFIG,
                                        Type.LONG,
                                        9 * 60 * 1000,
                                        Importance.MEDIUM,
                                        CommonClientConfigs.CONNECTIONS_MAX_IDLE_MS_DOC)
                                .define(PARTITIONER_CLASS_CONFIG,
                                        Type.CLASS,
                                        null,
                                        Importance.MEDIUM, PARTITIONER_CLASS_DOC)
                                .define(INTERCEPTOR_CLASSES_CONFIG,
                                        Type.LIST,
                                        Collections.emptyList(),
                                        new ConfigDef.NonNullValidator(),
                                        Importance.LOW,
                                        INTERCEPTOR_CLASSES_DOC)
                                .define(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG,
                                        Type.STRING,
                                        CommonClientConfigs.DEFAULT_SECURITY_PROTOCOL,
                                        in(Utils.enumOptions(SecurityProtocol.class)),
                                        Importance.MEDIUM,
                                        CommonClientConfigs.SECURITY_PROTOCOL_DOC)
                                .define(SECURITY_PROVIDERS_CONFIG,
                                        Type.STRING,
                                        null,
                                        Importance.LOW,
                                        SECURITY_PROVIDERS_DOC)
                                .withClientSslSupport()
                                .withClientSaslSupport()
                                .define(ENABLE_IDEMPOTENCE_CONFIG,
                                        Type.BOOLEAN,
                                        true,
                                        Importance.LOW,
                                        ENABLE_IDEMPOTENCE_DOC)
                                .define(TRANSACTION_TIMEOUT_CONFIG,
                                        Type.INT,
                                        60000,
                                        Importance.LOW,
                                        TRANSACTION_TIMEOUT_DOC)
                                .define(TRANSACTIONAL_ID_CONFIG,
                                        Type.STRING,
                                        null,
                                        new ConfigDef.NonEmptyString(),
                                        Importance.LOW,
                                        TRANSACTIONAL_ID_DOC);
    }

    @Override
    protected Map<String, Object> postProcessParsedConfig(final Map<String, Object> parsedValues) {
        CommonClientConfigs.postValidateSaslMechanismConfig(this);
        Map<String, Object> refinedConfigs = CommonClientConfigs.postProcessReconnectBackoffConfigs(this, parsedValues);
        postProcessAndValidateIdempotenceConfigs(refinedConfigs);
        maybeOverrideClientId(refinedConfigs);
        return refinedConfigs;
    }

    private void maybeOverrideClientId(final Map<String, Object> configs) {
        String refinedClientId;
        boolean userConfiguredClientId = this.originals().containsKey(CLIENT_ID_CONFIG);
        if (userConfiguredClientId) {
            refinedClientId = this.getString(CLIENT_ID_CONFIG);
        } else {
            String transactionalId = this.getString(TRANSACTIONAL_ID_CONFIG);
            refinedClientId = "producer-" + (transactionalId != null ? transactionalId : PRODUCER_CLIENT_ID_SEQUENCE.getAndIncrement());
        }
        configs.put(CLIENT_ID_CONFIG, refinedClientId);
    }

    private void postProcessAndValidateIdempotenceConfigs(final Map<String, Object> configs) {
        final Map<String, Object> originalConfigs = this.originals();
        final String acksStr = parseAcks(this.getString(ACKS_CONFIG));
        configs.put(ACKS_CONFIG, acksStr);
        final boolean userConfiguredIdempotence = this.originals().containsKey(ENABLE_IDEMPOTENCE_CONFIG);
        boolean idempotenceEnabled = this.getBoolean(ENABLE_IDEMPOTENCE_CONFIG);
        boolean shouldDisableIdempotence = false;

        // For idempotence producers, values for `retries` and `acks` and `max.in.flight.requests.per.connection` need validation
        if (idempotenceEnabled) {
            final int retries = this.getInt(RETRIES_CONFIG);
            if (retries == 0) {
                if (userConfiguredIdempotence) {
                    throw new ConfigException("Must set " + RETRIES_CONFIG + " to non-zero when using the idempotent producer.");
                }
                log.info("Idempotence will be disabled because {} is set to 0.", RETRIES_CONFIG);
                shouldDisableIdempotence = true;
            }

            final short acks = Short.parseShort(acksStr);
            if (acks != (short) -1) {
                if (userConfiguredIdempotence) {
                    throw new ConfigException("Must set " + ACKS_CONFIG + " to all in order to use the idempotent " +
                        "producer. Otherwise we cannot guarantee idempotence.");
                }
                log.info("Idempotence will be disabled because {} is set to {}, not set to 'all'.", ACKS_CONFIG, acks);
                shouldDisableIdempotence = true;
            }

            final int inFlightConnection = this.getInt(MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION);
            if (MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_FOR_IDEMPOTENCE < inFlightConnection) {
                if (userConfiguredIdempotence) {
                    throw new ConfigException("Must set " + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION + " to at most 5" +
                        " to use the idempotent producer.");
                }
                log.warn("Idempotence will be disabled because {} is set to {}, which is greater than 5. " +
                    "Please note that in v4.0.0 and onward, this will become an error.", MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, inFlightConnection);
                shouldDisableIdempotence = true;
            }
        }

        if (shouldDisableIdempotence) {
            configs.put(ENABLE_IDEMPOTENCE_CONFIG, false);
            idempotenceEnabled = false;
        }

        // validate `transaction.id` after validating idempotence dependant configs because `enable.idempotence` config might be overridden
        boolean userConfiguredTransactions = originalConfigs.containsKey(TRANSACTIONAL_ID_CONFIG);
        if (!idempotenceEnabled && userConfiguredTransactions) {
            throw new ConfigException("Cannot set a " + ProducerConfig.TRANSACTIONAL_ID_CONFIG + " without also enabling idempotence.");
        }
    }

    private static String parseAcks(String acksString) {
        try {
            return acksString.trim().equalsIgnoreCase("all") ? "-1" : Short.parseShort(acksString.trim()) + "";
        } catch (NumberFormatException e) {
            throw new ConfigException("Invalid configuration value for 'acks': " + acksString);
        }
    }

    static Map<String, Object> appendSerializerToConfig(Map<String, Object> configs,
            Serializer<?> keySerializer,
            Serializer<?> valueSerializer) {
        // validate serializer configuration, if the passed serializer instance is null, the user must explicitly set a valid serializer configuration value
        Map<String, Object> newConfigs = new HashMap<>(configs);
        if (keySerializer != null)
            newConfigs.put(KEY_SERIALIZER_CLASS_CONFIG, keySerializer.getClass());
        else if (newConfigs.get(KEY_SERIALIZER_CLASS_CONFIG) == null)
            throw new ConfigException(KEY_SERIALIZER_CLASS_CONFIG, null, "must be non-null.");
        if (valueSerializer != null)
            newConfigs.put(VALUE_SERIALIZER_CLASS_CONFIG, valueSerializer.getClass());
        else if (newConfigs.get(VALUE_SERIALIZER_CLASS_CONFIG) == null)
            throw new ConfigException(VALUE_SERIALIZER_CLASS_CONFIG, null, "must be non-null.");
        return newConfigs;
    }

    public ProducerConfig(Properties props) {
        super(CONFIG, props);
    }

    public ProducerConfig(Map<String, Object> props) {
        super(CONFIG, props);
    }

    ProducerConfig(Map<?, ?> props, boolean doLog) {
        super(CONFIG, props, doLog);
    }

    public static Set<String> configNames() {
        return CONFIG.names();
    }

    public static ConfigDef configDef() {
        return new ConfigDef(CONFIG);
    }

    public static void main(String[] args) {
        System.out.println(CONFIG.toHtml(4, config -> "producerconfigs_" + config));
    }

}
